#include "SDLControls.h"
#include "Logger.h"
#include <iostream>

using namespace std;

SDLControls* SDLControls::instance = NULL;

SDLControls::SDLControls(){

	//inicializo el indice de foco en menos uno
	focusIndex = -1;

	//inicializo el contador de widgets en pantalla
	onScreenWidgetCount = 0;

	//inicializo el array de widgets en pantalla (a reemplazarse por una lista)
	onScreenWidgets = new Widget*[MAX_ON_SCREEN_WIDGET_COUNT];

	//inicializo todo en NULL
	for(int widgetIndex = 0; widgetIndex < MAX_ON_SCREEN_WIDGET_COUNT; widgetIndex++)
		onScreenWidgets[widgetIndex] = NULL;

	//inicializo el hover (ningun widget esta bajo el mouse un un principio)
	hoveredWidget = NULL;

	//inicializo el handler de comienzo
	startHandler = NULL;

	//inicializo la pantalla mostrada
	screen = NULL;

	//inicalizo el array de handlers
	for(int handlerIndex = 0; handlerIndex < MAX_HANDLER_COUNT; handlerIndex++)
		globalHandlers[handlerIndex] = NULL;
}

SDLControls* SDLControls::getInstance(){

	if(instance == NULL)
		instance = new SDLControls();

	return instance;
}

int const SDLControls::getHRes(){
	return this->hRes;
}

void SDLControls::setHRes(const int& hRes){
	this->hRes = hRes;
}

int const SDLControls::getVRes(){
	return this->vRes;
}

void SDLControls::setVRes(const int& vRes){
	this->vRes = vRes;
}

int const SDLControls::getDepth(){
	return this->depth;
}

void SDLControls::setDepth(const int& depth){
	this->depth = depth;
}

void SDLControls::init(){

	//inicializo la SDL
	if(SDL_Init(SDL_INIT_EVERYTHING) == -1 ){
		std::stringstream ss ;
		ss << "SDL_Init : " << TTF_GetError() ;
		Logger::getInstance()->err(ss.str());
		Logger::getInstance()->flush();
		exit(2);
	}

	//inicializo la TTF
	if(TTF_Init()==-1) {
		std::stringstream ss ;
		ss << "TTF_Init : " << TTF_GetError() ;
		Logger::getInstance()->err(ss.str());
		Logger::getInstance()->flush();
		exit(2);
	}

	//inicializo los handlers para los eventos globales
	initGlobalHandlers();

	//inicializo la pantalla
	updateScreen();

	//inicializo el flag para esperar y manejar eventos en true
	this->keepLooping = true;

	//activo el modo unicode
	SDL_EnableUNICODE(SDL_ENABLE);
}

void SDLControls::initGlobalHandlers(){

	//inicializo todos los punteros a handler en NULL
	for(int globalHandlerIndex = 0; globalHandlerIndex < MAX_HANDLER_COUNT; globalHandlerIndex++)
		globalHandlers[globalHandlerIndex] = NULL;

}

void SDLControls::handleMouseMotion(Widget** widgets, const int& widgetCount, const SDL_Event& event){
	Widget* widget;

	//recorro los widgets y propago el event a los que corresponda segun su posicion en la pantalla
	for(int widgetIndex = 0; widgetIndex < widgetCount; widgetIndex++){

		//si el widget no es nulo
		widget = widgets[widgetIndex];
		if( widget != NULL ){

			//si el area del widget incluye el punto en el que esta el mouse
			if(widget->includesPoint(event.motion.x, event.motion.y)){

				//si el widget ya tenia el mouse encima no hago nada, de lo contrario, aviso al widget que el mouse acaba de posarse sobre el
				if(widget != hoveredWidget){

					//si no se trata de un contenedor, lo marco como el widget bajo el mouse actualmente
					if(!widget->isWidgetContainer()){

						//aviso al widget que tenia el mouse arriba anteriormente que el mouse lo "abandono" (si corresponde)
						if(hoveredWidget != NULL)
							hoveredWidget->mouseOut(event);

						//establezco el nuevo widget bajo el mouse
						hoveredWidget = widget;
					}

					//disparo el evento
					widget->mouseIn(event);
				}
			}
			else{

				//si el widget es un contenedor propago el evento
				if(widget->isWidgetContainer())
					widget->mouseOut(event);
				else{

					//si el widget no es un contenedor y estaba bajo el mouse reinicializo el puntero al widget bajo el mouse y disparo el evento correspondiente sobre el widget correspondiente
					if(widget == hoveredWidget){
						hoveredWidget = NULL;
						widget->mouseOut(event);
					}
				}
			}
		}
	}
}

void SDLControls::handleMouseClick(Widget** widgets, const int& widgetCount, const SDL_Event& event){
	Widget* widget;
	bool found;
	int widgetIndex;

	//inicializo el indice
	widgetIndex = 0;

	//inicializo el flag
	found = false;

	//disparo el evento click en el primer widget cuya area abarque la posicion del mouse (se supone que deberia ser uno solo)
	while(widgetIndex < widgetCount && !found)
		if( ( widget = widgets[widgetIndex++] ) != NULL && widget->includesPoint(event.motion.x, event.motion.y)){
			widget->click(event);
			found = true;
		}
}

void SDLControls::handleKeyDown(const SDL_Event& event){

	//si la tecla recibida fue tab entonces paso el foco al siguiente widget, de lo contrario, hago que el widget enfocado maneje el evento
	if(event.key.keysym.sym == SDLK_TAB)
		setNextFocusedWidget(event);
	else
		if(getFocusedWidget() != NULL)
			getFocusedWidget()->keyDown(event);
}

void SDLControls::handleEvent(const SDL_Event& event){
	if(globalHandlers[event.type] != NULL)
		globalHandlers[event.type](event);
}

void SDLControls::start(){
	SDL_Event event;

	//inicializa la SDL
	init();

	//si se registro el handler correspondiente, se invoca
	if(this->startHandler > 0)
		this->startHandler();

	//comienzo el loop principal
	while(this->keepLooping){

			//si hay un evento, lo manejo
			if(SDL_PollEvent(&event)){

				//realizo un manejo estático del evento necesario para el correcto funcionamiento del framework
				switch(event.type){
					case SDL_MOUSEMOTION:		handleMouseMotion(this->onScreenWidgets, this->onScreenWidgetCount, event); break;
					case SDL_MOUSEBUTTONDOWN:																				break;
					case SDL_MOUSEBUTTONUP:		handleMouseClick(this->onScreenWidgets, this->onScreenWidgetCount, event);	break;
					case SDL_KEYDOWN: 			handleKeyDown(event);														break;
					case SDL_KEYUP: 																						break;
					case SDL_QUIT: 				stop(); 																	break;
				}

				//manejo un manejo dinámico del evento de acuerdo a el manejador correspondiente que se haya establecido (si no se interrumpio el loop en el evento manejado)
				if(this->keepLooping)
					handleEvent(event);
			}

			// Para evitar que al salir se coma todo el micro.
			SDL_Delay(40);

	}

	//limpio la pantalla
	clear();

	//libero recursos
	clean();
}

void SDLControls::stop(){
	this->keepLooping = false;
}

void SDLControls::clean(){

	//desactivo unicode
	SDL_EnableUNICODE(SDL_DISABLE);

	//desactivo la TTF
	TTF_Quit();

	//desactivo la SDL
	SDL_Quit();

	//destruyo la instancia estatica
	delete instance;
}

void SDLControls::setStartHandler(SDL_CONTROLS_START_HANDLER_FUNC startHandler){
	this->startHandler = startHandler;
}

void SDLControls::setGlobalHandler(const Uint8& eventType, const SDL_EVENT_HANDLER_FUNC& handler){
	this->globalHandlers[eventType] = handler;
}

int SDLControls::getWidgetIndex(Widget* widget){
	bool found;
	int widgetIndex;

	//inicializo el indice
	widgetIndex = 0;

	//inicializo un flag para saber si lo encontre
	found = false;

	//mientras haya mas widgets y no lo haya encontrado
	while(!found && widgetIndex < onScreenWidgetCount)
		found = ( onScreenWidgets[widgetIndex++] == widget );

	if(!found)
		widgetIndex = -1;
	else
		widgetIndex--;

	return widgetIndex;
}

bool SDLControls::isWidgetOnScreen(Widget* widget){
	return getWidgetIndex(widget) != -1;
}

/**
 * Enfoca el widget recibido
 */
void SDLControls::setFocus(Widget* widget, const SDL_Event& event){

	//si habia un widget enfocado, lo desenfoco
	if(getFocusedWidget() != NULL)
		getFocusedWidget()->lostFocus(event);

	//si el widget que se quiere enfocar esta siendo mostrado
	if(isWidgetOnScreen(widget)){

		//lo registro como enfocado (me guardo su id)
		focusIndex = getWidgetIndex(widget);

		//le "aviso" que fue enfocado
		getFocusedWidget()->focus(event);
	}
}

void SDLControls::draw(Widget* widget, const int& x, const int& y){
	SDL_Rect widgetRect;

	//si el widget no es un contenedor y todavia no habia sido guardado, lo guardo
	if(!widget->isWidgetContainer() && !isWidgetOnScreen(widget)){
		focusIndex = onScreenWidgetCount;
		widget->setOnScreenWidgetId(onScreenWidgetCount);
		onScreenWidgets[onScreenWidgetCount] = widget;
		onScreenWidgetCount++;
	}

	//actualizo las coordenadas del widget
	widget->setX(x);
	widget->setY(y);

	//inicializo un rectangulo con las dimensiones del widget a dibujar
	widgetRect.x = widget->getX();
	widgetRect.y = widget->getY();
	widgetRect.h = widget->getHeight();
	widgetRect.w = widget->getWidth();

	//dibujo un rectangulo negro donde va a ir el widget
	//SDL_FillRect(this->screen, &widgetRect, SDL_MapRGB(SDL_GetVideoSurface()->format, 0x00, 0x00, 0x00));
	SDL_FillRect(this->screen, &widgetRect, SDL_MapRGB(SDL_GetVideoSurface()->format, widget->getBGColor().r, widget->getBGColor().g, widget->getBGColor().b));

	//dibujo el widget
	widget->draw(this->screen, x, y);

	//actualizo la porcion correspondiente de la pantalla
	SDL_UpdateRect(this->screen, x, y, widget->getWidth(), widget->getHeight());
}

void SDLControls::center(Widget* widget){
	draw(widget, (getHRes() - widget->getWidth())/2, (getVRes() - widget->getHeight())/2);
}

void SDLControls::clear(){

	//elimino los widgets
	for(int widgetIndex = 0; widgetIndex < MAX_ON_SCREEN_WIDGET_COUNT; widgetIndex++){
		if(onScreenWidgets[widgetIndex] != NULL){
			delete onScreenWidgets[widgetIndex];
			onScreenWidgets[widgetIndex] = NULL;
		}
	}

	//reseteo el indice al widget enfocado
	focusIndex = -1;

	//inicializo el contador de widgets mostrados
	onScreenWidgetCount = 0;

	//inicializo el puntero al widget sobrevolado por el mouse
	hoveredWidget = NULL;

	//limpio la pantalla
	SDL_FillRect(this->screen, NULL, 0 );

	//refresco
	SDL_UpdateRect(this->screen, 0, 0, this->hRes, this->vRes);
}

void SDLControls::setNextFocusedWidget(const SDL_Event& event){
	bool newFocusedWidgetIdFound = false;
	int widgetIndex;

	//busco el nuevo widget enfocado a partir del que ya estaba enfocado
	if(focusIndex < onScreenWidgetCount - 1)
		for(widgetIndex = focusIndex + 1; widgetIndex < onScreenWidgetCount; widgetIndex++)
			if( !newFocusedWidgetIdFound && onScreenWidgets[widgetIndex]->isFocusable()){
				newFocusedWidgetIdFound = true;
				setFocus(onScreenWidgets[widgetIndex], event);
			}

	//si no lo encontre, busco desde el principio
	if(/*widgetIndex == onScreenWidgetCount - 1 && */!newFocusedWidgetIdFound)
		for(widgetIndex = 0; widgetIndex < focusIndex; widgetIndex++)
			if( ( widgetIndex != focusIndex ) && !newFocusedWidgetIdFound && onScreenWidgets[widgetIndex]->isFocusable()){
				newFocusedWidgetIdFound = true;
				setFocus(onScreenWidgets[widgetIndex], event);
			}
}

Widget* SDLControls::getFocusedWidget(){
	return focusIndex>-1?this->onScreenWidgets[this->focusIndex]:NULL;
}

void SDLControls::updateScreen(){
	//inicializo la pantalla
	this->screen = SDL_SetVideoMode(getHRes(), getVRes(), getDepth(), SDL_SWSURFACE);
}

SDLControls::~SDLControls(){

	//limpio la pantalla
	//clear();

	//libero la memoria usada para guardar las referencias a los widgets en pantalla
	delete[] onScreenWidgets;
}
